﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using System.Configuration;
using System.IO;
using System.ServiceModel;
using System.Reflection;
using TesterCRM.Auxiliary;
using TesterCRM.Configuration;

namespace TesterCRM
{
    class Tester
    {
        private TestProjectsConfigSection section;
        private OrganizationService _orgService;
        private CrmConnection connection;
        private string connectionString;
        private string enabledProject;
        private string enabledConnection;

        /// <summary>
        /// Main method that administrates the testing workflow.
        /// </summary>
        public void Execute()
        {
            this.LoadProjectInfo();
            this.LoadCRMConnection();

            try
            {
                using ( _orgService = new OrganizationService(connection) )
                {
                    MethodInfo execute = null;
                    if ( this.CheckProjects(ref execute) )
                    {
                        execute.Invoke(this, new object[] { _orgService });
                    }
                    else
                    {
                        throw new Exception("There was any project matching.");
                    }
                }
            }
            catch ( FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> )
            {
                throw;
            }
            catch ( Exception ex )
            {
                throw ex;
            }
        }

        /// <summary>
        /// Loads the enabled project configurated on App.config.
        /// </summary>
        private void LoadProjectInfo()
        {
            try
            {
                section = (TestProjectsConfigSection)ConfigurationManager.GetSection("TestProjects");
            }
            catch
            {
                throw new Exception("There was a problem obtaining the project info from App.config.");
            }

            if ( string.IsNullOrWhiteSpace(section.EnabledProject.Name) )
                throw new Exception("The project name is null or empty.");

            if ( string.IsNullOrWhiteSpace(section.EnabledProject.Connection) )
                throw new Exception("The connection name is null or empty.");

            enabledProject = section.EnabledProject.Name;
            enabledConnection = section.EnabledProject.Connection;
        }

        /// <summary>
        /// Obtains the CRM conecction.
        /// </summary>
        private void LoadCRMConnection()
        {
            try
            {
                if ( string.IsNullOrWhiteSpace(enabledConnection) ) 
                    throw new Exception("enabledConnection is null or empty.");

                connectionString = Connection.GetServiceConfiguration(enabledConnection);
                connection = CrmConnection.Parse(connectionString);

                if ( connection.ServiceUri == null ||
                     !Uri.IsWellFormedUriString(connection.ServiceUri.AbsoluteUri, UriKind.Absolute) )
                    throw new Exception("The connection Uri is not correct.");
            }
            catch (Exception ex)
            {
                throw new Exception("There was a problem obtaining the CRM Connection.", ex);
            }
        }

        /// <summary>
        /// Checks which project is the selected and generates the info about the Execute method.
        /// </summary>
        /// <param name="execute">Reference to the execute method to launch in any project.</param>
        /// <returns></returns>
        private bool CheckProjects ( ref MethodInfo execute )
        {
            bool result = false;
            Assembly assembly = Assembly.GetExecutingAssembly();
            List<TypeInfo> projects = new List<TypeInfo>();

            Type tempType;
            Type itestType = typeof(ITest);

            // Check which projects implements the ITest interface.
            foreach ( var a in assembly.DefinedTypes )
            {
                tempType = a.GetTypeInfo();
                if ( itestType != tempType && itestType.IsAssignableFrom(a) )
                {
                    projects.Add(a);
                }
            }

            // The configurated project is found. Just only one (not multi-threading).
            foreach ( var p in projects )
            {
                if ( p.Name == enabledProject )
                {
                    tempType = p.GetTypeInfo();
                    execute = tempType.GetMethod("Execute");
                    result = true;
                    break;
                }
            }

            return result;
        }
    }
}